package com.linran.javaweb.annotation;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author tujiong
 */
@Slf4j
@Aspect
@Component
public class DistributedLockAop {

    private SpelExpressionParser spelExpressionParser;
    private ParameterNameDiscoverer paramNameDiscoverer;
//    private final ThreadLocal<RLock> lockMap = new ThreadLocal<>();
    private final ThreadLocal<Integer> lockCount = new ThreadLocal<>();

    @PostConstruct
    public void init() {
        spelExpressionParser = new SpelExpressionParser();
        paramNameDiscoverer = new DefaultParameterNameDiscoverer();
    }

    @Before("@annotation(DistributedLock)")
    public void acquireLock(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);

        String[] paramNames = paramNameDiscoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        EvaluationContext context = createEvaluationContext(paramNames, args);

        String bizKey = evaluateExpression(context, distributedLock.bizKey());
        String resourceKey = evaluateExpression(context, distributedLock.resourceKey());
        // 加锁
        acquireLock(bizKey, resourceKey, distributedLock.expiredTime(), distributedLock.timeUnit(), distributedLock.message());
    }

    @After("@annotation(DistributedLock)")
    public void unlock() {
        // 解锁
//        Integer count = lockCount.get();
//        if (Objects.equals(count, 0)) {
//            RLock rLock = lockMap.get();
//            rLock.unlock();
//            lockMap.remove();
//            lockCount.remove();
//        } else {
//            lockCount.set(--count);
//        }
    }

    private EvaluationContext createEvaluationContext(String[] paramNames, Object[] args) {
        EvaluationContext context = new StandardEvaluationContext();
        if (Objects.nonNull(paramNames)) {
            for (int i = 0; i < args.length; i++) {
                String pName = paramNames[i];
                if (!StringUtils.isEmpty(pName)) {
                    context.setVariable(pName, args[i]);
                }
            }
        }
        return context;
    }

    private String evaluateExpression(EvaluationContext context, String el) {
        Expression expression = spelExpressionParser.parseExpression(el);
        return String.valueOf(expression.getValue(context));
    }

    private void acquireLock(String bizKey, String resourceKey, long expiredTime, TimeUnit timeUnit, String message) {
        String lockKey = checkLockParam(bizKey, resourceKey);
        if (expiredTime < 0) {
            throw new RuntimeException("invalid expiredTime");
        }

//        RLock redLock = redissonClient.getLock(lockKey);
//        try {
//            redLock.tryLock(0, expiredTime, timeUnit);
//            lockMap.set(redLock);
//            Integer count = lockCount.get();
//            lockCount.set(Objects.isNull(count) ? 1 : ++count);
//        } catch (InterruptedException e) {
//            throw new BizException(resolveMessage(message));
//        }
    }

    private String checkLockParam(String bizKey, String resourceKey) {
        if (StringUtils.isEmpty(bizKey) || StringUtils.isEmpty(resourceKey)) {
            throw new RuntimeException("please specify bizKey and resourceKey");
        }
        return bizKey + resourceKey;
    }

    private String resolveMessage(String message) {
        // 如果message为空, 则返回默认信息
        if (StringUtils.isEmpty(message)) {
//            return MessageUtils.getMessage("lock.failed");
            return null;
        }

        String i18nMessageKey = i18nMessageKey(message);
        if (StringUtils.isEmpty(i18nMessageKey)) {
            return message;
        } else {
//            return MessageUtils.getMessage(i18nMessageKey);
            return null;
        }
    }

    final Pattern pattern = Pattern.compile("(?<=\\{).*(?=})");

    private String i18nMessageKey(String message) {
        if (StringUtils.isEmpty(message)) {
            return null;
        }

        Matcher matcher = pattern.matcher(message);
        if (matcher.find()) {
            return matcher.group();
        } else {
            return null;
        }
    }
}
